/**
	Pulsar engine. Core.
	Debug dialog boxes (assertion failed, memory error)

	Copyright (C) 2013-2014 Dmitry Dolgov

	This program is free software; you can redistribute it and/or modify it under the terms of the
	GNU General Public License as published by the Free Software Foundation; either version 2 of
	the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
	without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
	See the GNU General Public License for more details.
*/

#if !defined(PL_WINDOWS) && !defined(PL_WINDOWS64)
#error "This file can be user for Windows applications only"
#endif

#include <windows.h>
#include <mmsystem.h>
#include <shlobj.h>
#include <objbase.h>
#include <shlwapi.h>
#include <tlhelp32.h>

#include <pulsar/pulsar.h>
#include <pulsar/src/win/pulsar_intr_win.h>
#include <pulsar/src/win/pulsar_debug_win.h>

namespace pl
{

// Dialog boxes controls identifiers
enum
{
	IDC_EDIT_CONDITION = 100,
	IDC_EDIT_FILENAME,
	IDC_EDIT_FUNCTION,
	IDC_EDIT_DESCRIPTION,
	IDC_BUTTON_DEBUG,
	IDC_BUTTON_RETRY,
	IDC_BUTTON_QUIT,
	IDC_BUTTON_MANAGER,
	IDC_RADIO_RETRY,
	IDC_RADIO_IGNORE_THIS,
	IDC_RADIO_IGNORE_ALL,
	IDC_EDIT_NRETRY,
	IDC_LIST_ASSERTS,
	IDC_BUTTON_DELETE,
	IDC_CHECK_IGNORE_ALL,
	IDC_TEXT_CRITICAL,
	IDC_EDIT_POINTER,
	IDC_BUTTON_DUMP
};

AppDebugWin* AppDebugWin::_pClass = NULL;

} // namespace pl

/*
	Initialize debug functions
	Internal function - called from pulsar core
*/
bool pl::AppDebugWin::init()
{
	PL_ASSERTE(_pClass == NULL, PL_T("Secondary initialization"));
	_pClass = PL_NEW AppDebugWin;
	return true;
}

/*
	Shutdown debug functions
	Internal function - called from pulsar core
*/
void pl::AppDebugWin::term()
{
	PL_ASSERTE(_pClass != NULL, PL_T("Secondary shutdown"));
	PL_DELETE _pClass;
	_pClass = NULL;
	return;
}

/*
*/
pl::HandleResult pl::AppDebugWin::onAssert(const pl_char* file, pl_uint32 line, const pl_char* func, const pl_char* cond, const pl_char* msg, pl::AssertType type)
{
#if defined(PL_FINAL)
	// The function onAssert() was called unexpectedly in PL_FINAL build configuration
	// Report an error and ignore
	plLogExtError(file, line, PL_T("Assert(") << cond << PL_T(") failed: ") << msg);
	return HANDLE_RESULT_IGNORE;

#elif defined(PL_DEBUG) || defined(PL_RELEASE)

	if (_pClass == NULL || _pClass->_pDlgAssert == NULL)
	{
		// Class was not created or assertion dialog box was disabled
		return HANDLE_RESULT_DEBUG;
	}
	return _pClass->_doHandleAssert(file, line, func, cond, msg, type);

#else
#error "Wrong build configuration"
#endif
}

/*
*/
pl::HandleResult pl::AppDebugWin::onMemErr(const pl_char* file, pl_uint32 line, const pl_char* func, void* p, pl_int64 size, pl::MemoryError idEvent)
{
	if (_pClass == NULL || _pClass->_pDlgMemError == NULL)
	{
		// Class was not created or memory error dialog box was disabled
		return HANDLE_RESULT_DEBUG;
	}
	PL_BREAK(); // TODO
	return HANDLE_RESULT_IGNORE;
}

/*
*/
pl::AppDebugWin::AppDebugWin() :
_bIgnoreAllAsserts(false),
_pDlgAssert(NULL),
_pDlgMemError(NULL),
_pDlgOutMem(NULL),
_pDlgFatalError(NULL)
{
	// Create additional dialog boxes
	if (appOptions.windowMask & DEBUG_WINDOW_ASSERT)
	{
		RECT rect = {0, 0, 272, 116};
		_pDlgAssert = PL_NEW DlgTemplate(DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_SYSMODAL | DS_CENTER | WS_POPUP | WS_CAPTION, PL_T("Pulsar: Assertion failed"), rect, WS_EX_TOOLWINDOW | WS_EX_CONTROLPARENT);

		SetRect(&rect, 5, 5, 55, 10);
		_pDlgAssert->addControl(WS_VISIBLE, 0, rect, -1, WC_STATIC, PL_T("Assertion failed:"));
		SetRect(&rect, 65, 5, 195, 10);
		_pDlgAssert->addControl(WS_VISIBLE | ES_AUTOHSCROLL | ES_READONLY, 0, rect, IDC_EDIT_CONDITION, WC_EDIT, PL_T(""));
		SetRect(&rect, 5, 15, 55, 10);
		_pDlgAssert->addControl(WS_VISIBLE, 0, rect, -1, WC_STATIC, PL_T("File name, line #:"));
		SetRect(&rect, 65, 15, 195, 10);
		_pDlgAssert->addControl(WS_VISIBLE | ES_AUTOHSCROLL | ES_READONLY, 0, rect, IDC_EDIT_FILENAME, WC_EDIT, PL_T(""));
		SetRect(&rect, 5, 25, 55, 10);
		_pDlgAssert->addControl(WS_VISIBLE, 0, rect, -1, WC_STATIC, PL_T("Source function:"));
		SetRect(&rect, 65, 25, 195, 10);
		_pDlgAssert->addControl(WS_VISIBLE | ES_AUTOHSCROLL | ES_READONLY, 0, rect, IDC_EDIT_FUNCTION, WC_EDIT, PL_T(""));
		SetRect(&rect, 5, 35, 55, 10);
		_pDlgAssert->addControl(WS_VISIBLE, 0, rect, -1, WC_STATIC, PL_T("Text description:"));
		SetRect(&rect, 65, 35, 195, 10);
		_pDlgAssert->addControl(WS_VISIBLE | ES_AUTOHSCROLL | ES_READONLY, 0, rect, IDC_EDIT_DESCRIPTION, WC_EDIT, PL_T(""));
		SetRect(&rect, 65, 47, 195, 10);
		_pDlgAssert->addControl(WS_VISIBLE, 0, rect, IDC_TEXT_CRITICAL, WC_STATIC, PL_T(""));
		SetRect(&rect, 90, 95, 55, 15);
		_pDlgAssert->addControl(WS_VISIBLE, 0, rect, IDC_BUTTON_DEBUG, WC_BUTTON, PL_T("Debug"));
		SetRect(&rect, 150, 95, 55, 15);
		_pDlgAssert->addControl(WS_VISIBLE, 0, rect, IDC_BUTTON_RETRY, WC_BUTTON, PL_T("Ignore"));
		SetRect(&rect, 210, 95, 55, 15);
		_pDlgAssert->addControl(WS_VISIBLE, 0, rect, IDC_BUTTON_QUIT, WC_BUTTON, PL_T("Quit"));
		SetRect(&rect, 5, 95, 55, 15);
		_pDlgAssert->addControl(WS_VISIBLE, 0, rect, IDC_BUTTON_MANAGER, WC_BUTTON, PL_T("Assert list..."));
		SetRect(&rect, 5, 62, 135, 10);
		_pDlgAssert->addControl(WS_VISIBLE | BS_AUTORADIOBUTTON | WS_GROUP, 0, rect, IDC_RADIO_RETRY, WC_BUTTON, PL_T("Retry execution and skip this ASSERT"));
		SetRect(&rect, 5, 72, 170, 10);
		_pDlgAssert->addControl(WS_VISIBLE | BS_AUTORADIOBUTTON, 0, rect, IDC_RADIO_IGNORE_THIS, WC_BUTTON, PL_T("Ignore all ASSERT from this file/line")); 
		SetRect(&rect, 5, 82, 170, 10);
		_pDlgAssert->addControl(WS_VISIBLE | BS_AUTORADIOBUTTON, 0, rect, IDC_RADIO_IGNORE_ALL, WC_BUTTON, PL_T("Ignore all future ASSERTs in the current session")); 
		SetRect(&rect, 142, 61, 38, 12);
		_pDlgAssert->addControl(WS_VISIBLE | ES_AUTOHSCROLL | WS_TABSTOP | WS_BORDER | ES_NUMBER | WS_GROUP, 0, rect, IDC_EDIT_NRETRY, WC_EDIT, PL_T(""));
		SetRect(&rect, 185, 62, 20, 10);
		_pDlgAssert->addControl(WS_VISIBLE, 0, rect, -1, WC_STATIC, PL_T("times"));

	}
	if (appOptions.windowMask & DEBUG_WINDOW_MEMERR)
	{
		RECT  rect = {0, 0, 272, 62};
		_pDlgMemError = PL_NEW DlgTemplate(DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_SYSMODAL | DS_CENTER | WS_POPUP | WS_CAPTION, PL_T("Pulsar: Memory error"), rect, WS_EX_TOOLWINDOW);

		SetRect(&rect,   5,    5,  55, 10);
		_pDlgMemError->addControl(WS_VISIBLE, 0, rect, -1, WC_STATIC, PL_T("Error definition:"));
		SetRect(&rect,  65,   5, 195, 10);
		_pDlgMemError->addControl(WS_VISIBLE | ES_AUTOHSCROLL | ES_READONLY, 0, rect, IDC_EDIT_DESCRIPTION, WC_EDIT, PL_T(""));
		SetRect(&rect,   5,  15,  55, 10);
		_pDlgMemError->addControl(WS_VISIBLE, 0, rect, -1, WC_STATIC, PL_T("File name, line #:"));
		SetRect(&rect,  65,  15, 195, 10);
		_pDlgMemError->addControl(WS_VISIBLE | ES_AUTOHSCROLL | ES_READONLY, 0, rect, IDC_EDIT_FILENAME, WC_EDIT, PL_T(""));
		SetRect(&rect,   5,  25,  55, 10);
		_pDlgMemError->addControl(WS_VISIBLE, 0, rect, -1, WC_STATIC, PL_T("Source function:"));
		SetRect(&rect,  65,  25, 195, 10);
		_pDlgMemError->addControl(WS_VISIBLE | ES_AUTOHSCROLL | ES_READONLY, 0, rect, IDC_EDIT_FUNCTION, WC_EDIT, PL_T(""));
		SetRect(&rect,   5,  35,  55, 10);
		_pDlgMemError->addControl(WS_VISIBLE, 0, rect, -1, WC_STATIC, PL_T("Pointer address:"));
		SetRect(&rect,  65,  35, 195, 10);
		_pDlgMemError->addControl(WS_VISIBLE | ES_AUTOHSCROLL | ES_READONLY, 0, rect, IDC_EDIT_POINTER, WC_EDIT, PL_T(""));
		SetRect(&rect,  150, 45,  55, 15);
		_pDlgMemError->addControl(WS_VISIBLE, 0, rect, IDC_BUTTON_DEBUG, WC_BUTTON, PL_T("Debug"));
		SetRect(&rect,  210, 45,  55, 15);
		_pDlgMemError->addControl(WS_VISIBLE, 0, rect, IDC_BUTTON_QUIT,  WC_BUTTON, PL_T("Quit"));
		SetRect(&rect,    5, 45,  55, 15);
		_pDlgMemError->addControl(WS_VISIBLE, 0, rect, IDC_BUTTON_DUMP, WC_BUTTON, PL_T("Dump..."));
	}
	if (appOptions.windowMask & DEBUG_WINDOW_OUTMEM)
	{
	}
	if (appOptions.windowMask & DEBUG_WINDOW_FATAL_ERR)
	{
	}
	return;
}

pl::AppDebugWin::~AppDebugWin()
{
	DlgTemplate* temp;

	temp = _pDlgAssert;
	_pDlgAssert = NULL;
	PL_DELETE temp;
	temp = _pDlgMemError;
	_pDlgMemError = NULL;
	PL_DELETE temp;
	temp = _pDlgOutMem;
	_pDlgOutMem = NULL;
	PL_DELETE temp;
	temp = _pDlgFatalError;
	_pDlgFatalError = NULL;
	PL_DELETE temp;
	return;
}

pl::HandleResult pl::AppDebugWin::_doHandleAssert(const pl_char* file, pl_uint32 line, const pl_char* func, const pl_char* cond, const pl_char* msg, pl::AssertType type)
{
	static volatile unsigned long long enterCounter;
	pl::HandleResult rc = HANDLE_RESULT_IGNORE;

	switch (InterlockedIncrement(&enterCounter))
	{
		case 1:
			{
				// Looking for assertion
				AssertInfo* pInfo = _findAssertEvent(file, line, func, cond, msg, type);
				if (pInfo == NULL)
				{
					// Assertion list overflow, all other assertions will cause break immediately
					// TODO - customize size of assertion table
					rc = HANDLE_RESULT_DEBUG;
					break;
				}
				++pInfo->totalCount;
				if (_bIgnoreAllAsserts || pInfo->passCount == -1)
				{
					// Ignore all asserts or always ignore this assert
					rc = HANDLE_RESULT_IGNORE;
					break;
				}
				if (pInfo->passCount > 0)
				{
					// Ignore this assert N times
					--pInfo->passCount;
					rc = HANDLE_RESULT_IGNORE;
					break;
				}

				HWND hParentDialog = GetLastActivePopup(GetActiveWindow());
				rc = (pl::HandleResult)DialogBoxIndirectParam(pl::App::app->getHInstance(), _pDlgAssert->getGlobalMem(), hParentDialog, _assertWndFunc, (LPARAM)pInfo);
			}
			break;
		default:
			MessageBox(NULL, PL_T("Second assert failed. All asserts disabled"), PL_T("RedKey: Assertion failed"), MB_OK | MB_ICONSTOP);
			break;
	}
	InterlockedDecrement(&enterCounter);
	return rc;
}

/**
	Find a record about assertion event in this file/line
*/
pl::AssertInfo* pl::AppDebugWin::_findAssertEvent(const pl_char* file, pl_uint32 line, const pl_char* func, const pl_char* cond, const pl_char* msg, pl::AssertType type)
{
	pl_uint32 i;

	// Looking for assert event
	for (i=0; i<_assertList.size(); i++)
	{
		if (_assertList[i].line != line)
		{
			continue;
		}
		// Line compare passed, cheching filename
		if (pl_strcmp(_assertList[i].file, file) != 0)
		{
			continue;
		}
		return &_assertList[i];
	}

	// Not found, create new
	if (_assertList.isFull())
	{
		return NULL;
	}

	AssertInfo info;

	info.file = file;
	info.line = line;
	info.func = func;
	info.cond = cond;
	info.msg = msg;
	info.type = type;
	info.passCount = 0;
	info.totalCount = 0;
	_assertList.pushBack(info);
	return &_assertList[_assertList.size()-1];
}

INT_PTR CALLBACK pl::AppDebugWin::_assertWndFunc(HWND dlgHandle, UINT msg, WPARAM wParam, LPARAM lParam)
{
	AssertInfo* pInfo;

	switch (msg)
	{
		case WM_INITDIALOG:
			pInfo = (AssertInfo*)lParam;
			SetWindowLongPtr(dlgHandle, GWLP_USERDATA, lParam);

			{
				String<512> fileName;

				fileName << pInfo->file << PL_T(", #") << pInfo->line;
				SetDlgItemText(dlgHandle, IDC_EDIT_FILENAME, fileName.get());
				SetDlgItemText(dlgHandle, IDC_EDIT_FUNCTION, pInfo->func);
				SetDlgItemText(dlgHandle, IDC_EDIT_CONDITION, pInfo->cond);
				SetDlgItemText(dlgHandle, IDC_EDIT_DESCRIPTION, pInfo->msg);
				SetDlgItemText(dlgHandle, IDC_TEXT_CRITICAL, pInfo->type == ASSERT_TYPE_WARNING ? PL_T("PL_ASSERTW() macro") : PL_T("PL_ASSERTE() macro"));

				SendDlgItemMessage(dlgHandle, IDC_RADIO_RETRY, BM_SETCHECK, BST_CHECKED, 0);
				SetDlgItemInt(dlgHandle, IDC_EDIT_NRETRY, 0, FALSE);
			}
			return TRUE;
		case WM_COMMAND:
			if (wParam == IDC_BUTTON_DEBUG)
			{
				EndDialog(dlgHandle, HANDLE_RESULT_DEBUG);
				return TRUE;
			}
			if (wParam == IDC_BUTTON_RETRY)
			{
				pInfo = (AssertInfo*)GetWindowLongPtr(dlgHandle, GWLP_USERDATA);
				if (SendDlgItemMessage(dlgHandle, IDC_RADIO_RETRY, BM_GETCHECK, 0, 0) == BST_CHECKED)
				{
					pInfo->passCount = GetDlgItemInt(dlgHandle, IDC_EDIT_NRETRY, NULL, FALSE);
				}
				else if (SendDlgItemMessage(dlgHandle, IDC_RADIO_RETRY, IDC_RADIO_IGNORE_THIS, 0, 0) == BST_CHECKED)
				{
					pInfo->passCount = -1;
				}
				else if (SendDlgItemMessage(dlgHandle, IDC_RADIO_RETRY, IDC_RADIO_IGNORE_ALL, 0, 0) == BST_CHECKED)
				{
					_pClass->_bIgnoreAllAsserts = true;
				}
				EndDialog(dlgHandle, HANDLE_RESULT_IGNORE);
				return TRUE;
			}
			if (wParam == IDC_BUTTON_QUIT)
			{
				PL_FATAL_ERROR(-1, PL_T("Quit request from Assertion Dialog Box. Application terminated with error code -1."));
				EndDialog(dlgHandle, IDC_BUTTON_QUIT);
				return TRUE;
			}
			if (wParam == IDC_BUTTON_MANAGER)
			{
				// Show Assert manager
				// TODO
			}
	}
	return FALSE;
}

//
// End of file 'pulsar_debug_win.cpp'
//
